#include "WRApiRequester.h"
#include "Term.h"
#include "CategoryBlock.h"
#include "EntryBlock.h"
#include "Translation.h"
#include <QtNetwork>
#include <QtDebug>
#include <QVariant>
#include <QMap>
#include <QMapIterator>
#include <parser.h> // Parser de JSON
#include <QSettings>

WRApiRequester::WRApiRequester(const QString& word, QSettings* settings)
	: _word(word), _settings(settings), _error(false), _compounds(0)
{
	// We create the URL to request the translation to WordReference
	_url = QUrl(QString("%1%2/json/%3/%4").
		arg(_settings->value("WordReference/API/URL").toString()).
		arg(_settings->value("WordReference/API/key").toString()).
		arg(_settings->value("WordReference/API/Dictionary").toString()).
		arg(word));
	//"http://api.wordreference.com/{api_version}/{API_key}/json/{dictionary}/{term}"
	qDebug() << "Vamos a lanzar el request " << _url.toString();
}

WRApiRequester::~WRApiRequester()
{
	// Borramos el archivo temporal con la respuesta
	if (_responseFile) {
		delete _responseFile;
		_responseFile = 0;
	}
	QHashIterator<int, Term*> it(_terms);
	while (it.hasNext()) {
		it.next();
		delete it.value();
	}
	delete _compounds;
}

void WRApiRequester::startRequest()
{
	// Creamos un archivo temporal para recibir los datos de la respuesta
	_responseFile = new QTemporaryFile();
	if (!_responseFile->open()) {
		_error = true;
		_errorString = QString("No he podido crear un archivo temporal");
		delete _responseFile;
		_responseFile = 0;
		emit done();
		return;
	}
	// Hacemos un POST y nos preparamos para leer los contenidos de la respuesta
	_reply = _qnam.get(QNetworkRequest(_url));
	// Conecto la señal de que se ha terminado de recibir la respuesta desde el QNetworkReply
	connect(_reply, SIGNAL(finished()), this, SLOT(httpFinished()));
	// Conecto la señal de bytes disponibles del QNetworkReply
	connect(_reply, SIGNAL(readyRead()), this, SLOT(httpReadyRead()));
}

void WRApiRequester::httpReadyRead()
{
	// Escribimos en el temporal los datos leidos
	if (_responseFile)
		_responseFile->write(_reply->readAll());
}

void WRApiRequester::httpFinished()
{
	// Nos aseguramos de escribir los bytes pendientes que tuviesemos en el buffer
	_responseFile->flush();
	// Cerramos el archivo temporal
	_responseFile->close();

	// Comprobamos si hemos tenido una redirección
	QVariant redirectionTarget = _reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
	// Si hemos tenido un error, borramos el archivo temporal
	if (_reply->error()) {
		_error = true;
		_errorString = QString("Error recibiendo la respuesta");
		if (_responseFile) {
			delete _responseFile;
			_responseFile = 0;
		}
		emit done();
	} else if (!redirectionTarget.isNull()) {
		// Si se ha producido una redirección
		QUrl newUrl = _url.resolved(redirectionTarget.toUrl());
		qDebug() << "Nos han redirigido a " << newUrl.toString();
		_url = newUrl;
		_reply->deleteLater();
 		delete _responseFile;
		 _responseFile = 0;
		startRequest();
		return;
	} else {
		// Ya tenemos respuesta a nuestra peticion
		qDebug() << "Tenemos una respuesta recibida";
		// Parseamos la respuesta que hemos recibido
		parseResponseJSON();
		emit done();
	}

	// Borramos la respuesta y el temporal
	_reply->deleteLater();
	_reply = 0;
}

bool WRApiRequester::parseResponseJSON()
{
	QJson::Parser parser; // Parser para leer la respuesta JSON
	bool ok;
	// La respuesta JSON recibida en el archivo temporal está compuesta de una serie de objetos
	QMap<QString, QVariant> resultObjects = parser.parse(_responseFile, &ok).toMap();
	if (ok) {
		// Nos aseguramos de que la respuesta recibida sea completa. Para ello tenemos que tener el objeto "Lines" con
		// un objeto "Maximum Reached" y el objeto "END" con otro objeto "true"
		if ( !resultObjects.contains("Lines") ||
		     !resultObjects.contains("END") ||
		     !(resultObjects.value("Lines").toString() == "End Reached") ||
		     !(resultObjects.value("END").toString() == "true") ) {
			_error = true;
			_errorString = QString("La respuesta recibida no esta completa");
			return false;
		}
		qDebug() << "La respuesta recibida es completamente correcta";

		// Iteramos por todos los objetos que contiene la respuesta
		QMapIterator<QString, QVariant> resultObjectsIterator(resultObjects);
		resultObjectsIterator.toBack();
		while (resultObjectsIterator.hasPrevious()) {
			resultObjectsIterator.previous();
			// Si no se trata de un objeto "term#" o un objeto "original" me paso al siguiente objeto
			if ( !resultObjectsIterator.key().startsWith("term") &&
			     !(resultObjectsIterator.key() == "original") ) {
				continue;
			}

			// Si es un nuevo Term
			Term *newTerm;
			if (resultObjectsIterator.key().startsWith("term")) {
				newTerm = new Term();
			}
			// Dentro de cada objeto term# o Compounds me puedo encontrar varios category-block
			QMap<QString, QVariant> categoryBlocksList = resultObjectsIterator.value().toMap();
			QMapIterator<QString, QVariant> categoryBlocksIterator(categoryBlocksList);
			categoryBlocksIterator.toBack();
			while (categoryBlocksIterator.hasPrevious()) {
				categoryBlocksIterator.previous();
				// Creo un nuevo CategoryBlock
				CategoryBlock *newCategoryBlock = new CategoryBlock();
				// Dentro de un category-block me puedo encontrar varios objetos
				QMap<QString, QVariant> categoryBlockObjectsList = categoryBlocksIterator.value().toMap();
				QMapIterator<QString, QVariant> categoryBlockObjectsListIterator(categoryBlockObjectsList);
				categoryBlockObjectsListIterator.toFront();
				while (categoryBlockObjectsListIterator.hasNext()) {
					categoryBlockObjectsListIterator.next();
					int entryNumber = categoryBlockObjectsListIterator.key().toInt();
					EntryBlock *newEntryBlock = new EntryBlock();
					newEntryBlock->setName(categoryBlockObjectsListIterator.key());
					// Cada uno de estos objetos lleva asociado un entry-block
					QMap<QString, QVariant> entryBlockList = categoryBlockObjectsListIterator.value().toMap();
					QMapIterator<QString, QVariant> entryBlockListIterator(entryBlockList);
					entryBlockListIterator.toFront();
					while (entryBlockListIterator.hasNext()) {
						entryBlockListIterator.next();
						if (entryBlockListIterator.key() == "Note") { // Se trata de una nota
							newEntryBlock->setNote(entryBlockListIterator.value().toString());
						} else { // Cualquiera de las otras opciones tiene dentro un Translation
							Translation *newTranslation = new Translation();
							// Dentro de un entry-block me puedo encontrar varios Terms o una nota
							QMap<QString, QVariant> entryBlockObjectList = entryBlockListIterator.value().toMap();
							QMapIterator<QString, QVariant> entryBlockObjectListIterator(entryBlockObjectList);
							entryBlockObjectListIterator.toFront();
							while (entryBlockObjectListIterator.hasNext()) {
								entryBlockObjectListIterator.next();
								if (entryBlockObjectListIterator.key() == "term") { // Término
									newTranslation->setTerm(entryBlockObjectListIterator.value().toString());
								} else if (entryBlockObjectListIterator.key() == "POS") { // Part Of Speech
									newTranslation->setPartOfSpeech(entryBlockObjectListIterator.value().toString());
								} else if (entryBlockObjectListIterator.key() == "sense") { // Sentido del término
									newTranslation->setSense(entryBlockObjectListIterator.value().toString());
								} else if (entryBlockObjectListIterator.key() == "usage") { // Notas de uso
									newTranslation->setUsage(entryBlockObjectListIterator.value().toString());
								}
							}
							newTranslation->setName(entryBlockListIterator.key());
							// Añado el term al EntryBlock donde corresponda
							if (entryBlockListIterator.key() == "OriginalTerm") { // Se trata de un entry-block "OriginalTerm"
								newEntryBlock->addTranslation("OriginalTerm", newTranslation);
							} else if (entryBlockListIterator.key() == "FirstTranslation") { // Se trata de un entry-block "FirstTranslation"
								newEntryBlock->addTranslation("FirstTranslation", newTranslation);
							} else if (entryBlockListIterator.key() == "SecondTranslation") { // Se trata de un entry-block "SecondTranslation"
								newEntryBlock->addTranslation("SecondTranslation", newTranslation);
							} else if (entryBlockListIterator.key() == "ThirdTranslation") { // Se trata de un entry-block "ThirdTranslation"
								newEntryBlock->addTranslation("ThirdTranslation", newTranslation);
							} else if (entryBlockListIterator.key() == "FourthTranslation") { // Se trata de un entry-block "FourthTranslation"
								newEntryBlock->addTranslation("FourthTranslation", newTranslation);
							}
						}
					}
					// Añado la entrada en la categoría
					newCategoryBlock->addEntry(entryNumber, newEntryBlock);
				}
				newCategoryBlock->setName(categoryBlocksIterator.key());
				if (categoryBlocksIterator.key() == "PrincipalTranslations") { // Se trata de un category-block "PrincipalTranslations"
					newTerm->addCategory("PrincipalTranslations", newCategoryBlock);
				} else if (categoryBlocksIterator.key() == "AdditionalTranslations") { // Se trata de un category-block "AdditionalTranslations"
					newTerm->addCategory("AdditionalTranslations", newCategoryBlock);
				} else if (categoryBlocksIterator.key() == "Entries") { // Se trata de un category-block "Entries"
					newTerm->addCategory("Entries", newCategoryBlock);
				} else if (categoryBlocksIterator.key() == "OtherSideEntires") { // Se trata de un category-block "OtherSideEntires"
					newTerm->addCategory("OtherSideEntires", newCategoryBlock);
				} else if (categoryBlocksIterator.key() == "Compounds") { // Se trata de un category-block "Compounds"
					_compounds = newCategoryBlock;
				}
			}
			// Si el objeto que me he encontrado es un term, lo añado al listado
			if (resultObjectsIterator.key().startsWith("term")) {
				newTerm->setName(resultObjectsIterator.key());
				_terms.insert(resultObjectsIterator.key().right(1).toInt(), newTerm);
			}
		}
		return true;
	} else {
		_error = true;
		_errorString = QString("El parser ha fallado");
		return false;
	}
}

void WRApiRequester::dump(QXmlStreamWriter& writer)
{
	QString wrTerm;
	// Las categorías de una respuesta pueden venir divididas entre PrincipalTranslations y AdditionalTranslations o venir en
	// Entries. Compruebo su existencia
	if (!_terms.value(0)->getCategory("PrincipalTranslations")) {
		wrTerm = _terms.value(0)->getCategory("Entries")->getEntry(0)->getTranslation("OriginalTerm")->getTerm();
	} else {
		wrTerm = _terms.value(0)->getCategory("PrincipalTranslations")->getEntry(0)->getTranslation("OriginalTerm")->getTerm();
	}
	// Pinto el término buscado y el original
	writer.writeTextElement("h2", QString("%1 (%2)").arg(wrTerm).arg(_word));

	for (int i=0; i<_terms.size(); ++i) {
		_terms.value(i)->dump(writer);
	}

	if (_compounds) {
		// Add horizontal line
		writer.writeStartElement("div");
		writer.writeAttribute("class", "hr");
		writer.writeTextElement("hr", "");
		writer.writeEndElement(); // div
		_compounds->dump(writer);
	}
}

